package com.ninth.element.generated.number;

import com.ninth.element.generated.number.generate.fallback.DefaultIdFallback;
import com.ninth.element.generated.number.generate.redis.RedisIdTemplate;
import com.ninth.element.generated.number.generate.redis.RedisIdWorker;
import com.ninth.element.generated.number.property.RedisLettucePoolWithNumberProperty;
import com.ninth.element.generated.number.property.RedisLettuceWithNumberProperty;
import com.ninth.element.generated.number.property.RedisWithNumberProperty;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 自动配置类
 *
 * @author LiYUJi
 **/
@EnableConfigurationProperties(value = {RedisWithNumberProperty.class,
        RedisLettuceWithNumberProperty.class,
        RedisLettucePoolWithNumberProperty.class})
@ComponentScan(basePackages = "com.ninth.element.generated.number.generate.common.pojo.enums")
@ConditionalOnProperty(prefix = NumberStarterConstants.NUMBER_REDIS_PREFIX, name = "enable", havingValue = "true")
@Configuration
public class GeneratedNumberRedisAutoConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(GeneratedNumberRedisAutoConfiguration.class);

    private final Environment environment;

    public GeneratedNumberRedisAutoConfiguration(Environment environment) {
        this.environment = environment;
    }


    @Bean(name = "numberGenericObjectPoolConfig")
    @ConditionalOnMissingBean(name = "numberGenericObjectPoolConfig")
    public GenericObjectPoolConfig numberGenericObjectPoolConfig(RedisLettucePoolWithNumberProperty redisLettucePoolWithNumberProperty, RedisWithNumberProperty redisWithNumberProperty) {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(redisLettucePoolWithNumberProperty.getMaxActive());
        config.setMaxIdle(redisLettucePoolWithNumberProperty.getMaxIdle());
        config.setMinIdle(redisLettucePoolWithNumberProperty.getMinIdle());
        config.setMaxWaitMillis(redisLettucePoolWithNumberProperty.getMaxWait());
        config.setTestOnBorrow(redisWithNumberProperty.getTestOnBorrow());
        config.setTestWhileIdle(redisWithNumberProperty.getTestWhileIdle());
        return config;
    }


    @Bean(name = "numberLettuceConnectionFactory")
    @ConditionalOnMissingBean(name = "numberLettuceConnectionFactory")
    @ConditionalOnBean(name = "numberGenericObjectPoolConfig")
    public LettuceConnectionFactory numberLettuceConnectionFactory(@Qualifier("numberGenericObjectPoolConfig") GenericObjectPoolConfig numberGenericObjectPoolConfig,
                                                                   RedisLettuceWithNumberProperty redisLettuceWithNumberProperty,
                                                                   RedisWithNumberProperty redisWithNumberProperty) {
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofSeconds(redisLettuceWithNumberProperty.getCommandTimeout()))
                .shutdownTimeout(Duration.ofMillis(redisLettuceWithNumberProperty.getShutdownTimeout()))
                .poolConfig(numberGenericObjectPoolConfig).build();
        String host = getPropertyOrEnvironment(redisWithNumberProperty.getHost(), NumberStarterConstants.SPRING_REDIS_HOST);
        int port = getPropertyOrEnvironment(redisWithNumberProperty.getPort(), NumberStarterConstants.SPRING_REDIS_PORT);
        String password = getPropertyOrEnvironment(redisWithNumberProperty.getPassword(), NumberStarterConstants.SPRING_REDIS_PASSWORD);
        String clusterNodes = redisWithNumberProperty.getNodes();

        if (ObjectUtils.isEmpty(clusterNodes)) {
            //单机配置
            RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
            redisStandaloneConfiguration.setHostName(host);
            if (!ObjectUtils.isEmpty(password)) {
                redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
            }
            redisStandaloneConfiguration.setPort(port);
            redisStandaloneConfiguration.setDatabase(redisWithNumberProperty.getDatabase());
            return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfiguration);
        } else {
            //集群配置
            Map<String, Object> source = new HashMap<>(8);
            source.put("spring.redis.cluster.nodes", clusterNodes);
//            source.put("spring.redis.cluster.timeout", environment.getProperty("spring.bid.redis.cluster.timeout"));
//            source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.bid.redis.cluster.max-redirects"));
            RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
            if (!ObjectUtils.isEmpty(password)) {
                redisClusterConfiguration.setPassword(RedisPassword.of(password));
            }

            return new LettuceConnectionFactory(redisClusterConfiguration, clientConfiguration);
        }
    }


    /**
     * 配置redis
     *
     * @param numberLettuceConnectionFactory numberLettuceConnectionFactory
     * @return StringRedisTemplate
     */
    @Bean("numberStringRedisTemplate")
    @ConditionalOnMissingBean(name = "numberStringRedisTemplate")
    @ConditionalOnBean(name = "numberLettuceConnectionFactory")
    public StringRedisTemplate numberStringRedisTemplate(@Qualifier("numberLettuceConnectionFactory") RedisConnectionFactory numberLettuceConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(numberLettuceConnectionFactory);
        return template;
    }


    @Bean
    @ConditionalOnMissingBean
    public DefaultRedisScript<Long> numberDefaultRedisScript() {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setResultType(Long.class);
        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/nextuuid.lua")));
        return defaultRedisScript;
    }


    @Bean
    @ConditionalOnMissingBean
    public DefaultIdFallback defaultIdFallback() {
        return new DefaultIdFallback();
    }


    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(name = "numberStringRedisTemplate")
    public RedisIdWorker redisIdWorker() {
        return new RedisIdWorker();
    }


    @Bean
    @ConditionalOnBean(RedisIdWorker.class)
    RedisIdTemplate redisIdTemplate() {
        return new RedisIdTemplate();
    }


    /**
     * 判断如何属性为null 则获取容器中 spring的属性
     *
     * @param property        获取对象的属性
     * @param environmentName spring容器中的key
     * @return String
     */
    private String getPropertyOrEnvironment(String property, String environmentName) {
        if (StringUtils.isEmpty(property)) {
            return environment.getProperty(environmentName, "");
        } else {
            return property;
        }
    }

    private int getPropertyOrEnvironment(int property, String environmentName) {
        if (StringUtils.isEmpty(property)) {
            return environment.getProperty(environmentName, Integer.class, 6379);
        } else {
            return property;
        }
    }

}
